#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@author: stephen li
@contact: lishb0523@163.com
@version: 1.0.0
@license: Apache Licence
@file: ly_burn_tool.py
@time: 2023/10/13 17:17
"""

from serial import *
import time
import json
import fire

version = "1.1.0"


def get_devinfo(siteno):
    with open(".\devconfig.json", 'r', encoding='utf-8') as f:
        deviceinfo = json.load(f)[siteno]
    return deviceinfo

def get_otp(dev):
    cmds = [
        "AT+ECHO=1",
        "AT+LOAD=2",
        "AT+RUN",
        # "AT+PATDATAR"
    ]
    cmds = [(c.strip()+"\r\n").encode("utf-8") for c in cmds]
    resp = []
    with Serial(dev["port"], dev["baudrate"], timeout=0.5) as com:
        for cmd in cmds:
            com.write(cmd)
            readlines = 10
            while True:
                readlines -=1
                ret = com.readline()
                print(ret.decode(), end="")
                if ret.decode().startswith("OK") or ret.decode().startswith("Error["):
                    break
                if readlines==0:
                    raise TimeoutError("Too many response from device!")
            time.sleep(0.05)
        time.sleep(0.3)

        com.write("AT+PATDATAR=0,26\r\n".encode())
        readlines = 20
        while True:
            readlines -=1
            ret = com.readline()
            resp.append(ret)
            print(ret.decode(), end="")
            if ret.decode().startswith("OK") or ret.decode().startswith("Error["):
                break
            if readlines==0:
                raise TimeoutError("Too many response from device!")
    q_all = []
    for line in resp:
        line = line.decode()
        if line.find(":")>0:
            val = line.strip().split(": ")[1].split(" ")
            q_all.extend(val)
            # print(val)
    q_all = [int(v, 16) for v in q_all]
    q1 = q_all[0:5]
    q0 = q_all[6:]
    return q0, q1

def burn_q0(data, dev, trymode):
    assert len(data) == 20, "data length not correctly!"
    cmds = [
        "AT+ECHO=1",
        "AT+LOAD=1",
    ]
    for n, v in enumerate(data):
        cmds.append(f"AT+PATDATA={n + 6},{v},255")
    if trymode:
        cmds.append("AT+RUN")
    print("start brun q0!")
    cmds = [(c.strip()+"\r\n").encode("utf-8") for c in cmds]
    with Serial(dev["port"], dev["baudrate"], timeout=0.5) as com:
        for cmd in cmds:
            com.write(cmd)
            readlines = 10
            while True:
                readlines -=1
                ret = com.readline()
                print(ret.decode(), end="")
                if ret.decode().startswith("OK") or ret.decode().startswith("Error["):
                    break
                if readlines==0:
                    raise TimeoutError("Too many response from device!")
            time.sleep(0.05)
        time.sleep(0.5)
    # read and check
    q0,_ = get_otp(dev)
    print(u"给定数据:", data)
    if q0 != data:
        print(u"回读数据:", q0)
        print("---  Fail!  ---")
    else:
        print("************************************************")
        print("*                    PASS                      *")
        print("************************************************")

def read_aps(apsfile):
    if(not apsfile.endswith(".APS")):
        raise FileExistsError("Only support input .APS file.")
    with open(os.path.join(apsfile), 'r') as f:
        line = f.readline()
        # print(line)
        ic100_bin = line.split(";")[0].split("-")[0]
        ic200_bin = line.split(";")[2].split("-")[0]
        # print(ic100_bin)
        # print(ic200_bin)
        ic100_hex = hex(int(ic100_bin[::], 2))
        ic200_hex = hex(int(ic200_bin[::], 2))
        # print(ic100_hex)
        # print(ic200_hex)
        ic100_otp_q0 = list(bytearray.fromhex('0'+ic100_hex[2::]))[::-1]
        ic200_otp_q0 = list(bytearray.fromhex('0'+ic200_hex[2::]))[::-1]
        print("ic100: ", ic100_otp_q0)
        print("ic200: ", ic200_otp_q0)
        return ic100_otp_q0, ic200_otp_q0

def get_bytes(bincode: str):
    ic100_hex = hex(int(bincode[::], 2))
    ic100_otp_q0 = list(bytearray.fromhex('0'+ic100_hex[2::]))[::-1]
    return ic100_otp_q0


def burn_go(bindata: str, trymode:int=0, site:int=0):
    """
    burn otp on site n. Please update content in devconfig.json before running.
    :param bindata: the binary data from aps file, 156 bits, origin data, DO NOT change it's order. MSB left.
    :param trymode: try to run script, but not really burn into chip. 0: not burn; 1: burn.
    :param site: site number from 0 to max. according to the order of devconfig.json, which serial port to use.
    :return: No return.
    """
    dev = get_devinfo(site)
    if bindata == -1:
        q0_pre, _ = get_otp(dev)
        if all([v == 255 for v in q0_pre[:-1]]):
            print(u"数据读取错误，请检查芯片是否放好！！！")
        else:
            print(u"回读数据:", q0_pre)
        return
    if isinstance(bindata, str):
        assert len(bindata) == 156, f"给定数据长度不是156bits [{len(bindata)}]"
    else:
        raise ValueError(u"数据类型错误！！！")
    otpdata = get_bytes(bindata)
    q0_pre, q1_pre = get_otp(dev)
    attempt = 3
    while all([v==0 for v in q0_pre]) and all([v==0 for v in q1_pre]):
        q0_pre, q1_pre = get_otp(dev)
        attempt -= 1
        if attempt == 0:
            raise SystemError(u"---------读取芯片数据失败3次，请检查硬件环境------------")

    if all([v==255 for v in q0_pre[:-1]]):
        print(u"数据读取错误，请检查芯片是否放好！！！")
    elif all([v==0 for v in q0_pre]) and any([v!=0 for v in q1_pre]):
        burn_q0(otpdata, dev, trymode)
    else:
        print(u"给定数据:", otpdata)
        print(u"回读数据:", q0_pre)
        print(u"===  已被烧录的芯片  ===")
        if q0_pre != otpdata:
            print(u"---  数据比对：Fail!  ---")
        else:
            print(u"***  数据比对：PASS  ***")



if __name__ == '__main__':
    fire.Fire(burn_go)
